extends RefCounted
class_name AbstractCsv        # 抽象基类

## --------------------------------------------------
## 可修改的公共参数
## --------------------------------------------------
@export var delimiter   : String = ","    # 分隔符
@export var quote_char  : String = "\""   # 引号
@export var has_header  : bool   = true   # 第一行是否为表头

## --------------------------------------------------
##               外部主接口
## --------------------------------------------------
func load(path:String) -> Array:
	# 读取文件 → Array[Variant]（由 _row_to_object 决定）
	var file := FileAccess.open(path, FileAccess.READ)
	if file == null:
		push_error("AbstractCsv: cannot open %s" % path)
		return []

	var header:PackedStringArray = []
	var objects:Array = []

	if has_header and not file.eof_reached():
		header = _parse_line(file.get_line())

	while not file.eof_reached():
		var line := file.get_line()
		if line == "":    # 跳过空行
			continue
		var fields := _parse_line(line)
		var obj = _row_to_object(fields, header)
		if obj != null:
			objects.append(obj)

	file.close()
	return objects


func save(path:String, objects:Array) -> void:
	# 把 objects 写成 CSV（由 _object_to_row 决定）
	var file := FileAccess.open(path, FileAccess.WRITE)
	if file == null:
		push_error("AbstractCsv: cannot open %s for write" % path)
		return

	var header := _get_header(objects)
	if has_header and header.size() > 0:
		file.store_line(_join_line(header))

	for obj in objects:
		var row := _object_to_row(obj, header)
		file.store_line(_join_line(row))

	file.close()


## --------------------------------------------------
##             “必须”由子类覆写的钩子
## --------------------------------------------------
func _row_to_object(_fields:PackedStringArray, _header:PackedStringArray) -> Variant:
	push_error("%s needs to override _row_to_object()" % self)
	return null

func _object_to_row(_obj:Variant, _header:PackedStringArray) -> PackedStringArray:
	push_error("%s needs to override _object_to_row()" % self)
	return PackedStringArray()

## 如果不想写表头，可让 _get_header 返回空数组
func _get_header(objects:Array) -> PackedStringArray:
	# 1. 没数据就返回空
	if objects.is_empty():
		return PackedStringArray()

	# 2. 必须确保首元素是 Dictionary
	if not (objects[0] is Dictionary):
		return PackedStringArray()   # 或者 push_error + 返回空

	var dict : Dictionary = objects[0]

	# 3. 取出键并排序（Array）
	var keys : Array = dict.keys()
	keys.sort()

	# 4. 转成 PackedStringArray；保证都是字符串
	var header := PackedStringArray()
	for k in keys:
		header.append(str(k))
	return header


## --------------------------------------------------
##            行→字段  /  字段→行  通用工具
## --------------------------------------------------
func _parse_line(line:String) -> PackedStringArray:
	var result:PackedStringArray = []
	var field := ""
	var in_quotes := false
	var i := 0

	while i < line.length():
		var c := line[i]
		if c == quote_char:
			# 双引号转义
			if in_quotes and i + 1 < line.length() and line[i + 1] == quote_char:
				field += quote_char
				i += 1
			else:
				in_quotes = !in_quotes
		elif c == delimiter and not in_quotes:
			result.append(field)
			field = ""
		else:
			field += c
		i += 1
	result.append(field)
	return result


func _escape_field(text:String) -> String:
	var needs_quote :=  text.find(delimiter) != -1 \
					 or text.find(quote_char) != -1 \
					 or text.find("\n") != -1
	if needs_quote:
		var escaped := text.replace(quote_char, quote_char + quote_char)
		return quote_char + escaped + quote_char
	return text


func _join_line(fields:Array) -> String:
	var out := PackedStringArray()
	for f in fields:
		out.append(_escape_field(str(f)))
	return delimiter.join(out)
